package cc.rengu.oltp.utility.util;

import org.apache.commons.codec.binary.Base64;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Random;

/**
 * 图片验证码工具类
 * Created by 王成 on 2020/3/26
 */
public class ImageCodeUtil implements Serializable {
    private final Random random = new Random();
    private final String[] fontNames = {"宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312"};
    private final Color backGroudColor = new Color(255, 255, 255);
    private BufferedImage image;

    public String generateImageCode(int length) {
        return this.generateImageCode(length, 70, 35, false);
    }

    /**
     * 生成图片验证码
     *
     * @param length    长度
     * @param width     宽度
     * @param height    高度
     * @param shearFlag 是否扭曲图片
     * @return 图片验证码
     */
    public String generateImageCode(int length, int width, int height, boolean shearFlag) {
        BufferedImage verifyImage = createImage(width, height); //创建图片缓冲区
        Graphics2D graphics = (Graphics2D) verifyImage.getGraphics();//得到绘制环境，画笔
        StringBuilder stringBuilder = new StringBuilder();//用来装载生成的验证码文本
        length = Math.max(length, 4);
        int fx = 0;
        int fsize = (int) (height * 0.8);//字体大小为图片高度的80%
        for (int i = 0; i < length; i++) {
            String randString = randomChar() + "";   //随机生成一个字符
            stringBuilder.append(randString);
            int fy = (int) ((Math.random() * 0.3 + 0.6) * height);//每个字符高低随机
            graphics.setFont(randomFont(fsize));  //设置随机字体
            graphics.setColor(randomColor()); //设置随机颜色
            graphics.drawString(randString, fx, fy);  //画图
            fx += (width / length) * (Math.random() * 0.3 + 0.8); //依据宽度浮动
        }
        drawLine(verifyImage, width, height); //添加干扰线
        if (shearFlag) {   //是否扭曲图片
            shearX(graphics, width, height, backGroudColor);
            shearY(graphics, width, height, backGroudColor);
        }
        this.image = verifyImage;
        return stringBuilder.toString();
    }

    /**
     * 获取图片验证码图片字符串(Base64加密)
     *
     * @return 图片验证码流
     * @throws IOException 异常
     */
    public String getImageString() throws IOException {
        return getImageString("jpg");
    }

    /**
     * 获取图片验证码图片字符串(Base64加密)
     *
     * @param imageType 图片格式(jpg,jepg)
     * @return 图片验证码流
     * @throws IOException 异常
     */
    public String getImageString(String imageType) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ImageIO.write(this.image, imageType, byteArrayOutputStream);
        return Base64.encodeBase64String(byteArrayOutputStream.toByteArray());
    }

    /**
     * 生成随机颜色
     *
     * @return 随机颜色
     */
    private Color randomColor() {
        int red = random.nextInt(150);
        int green = random.nextInt(150);
        int blue = random.nextInt(150);
        return new Color(red, green, blue);
    }

    /**
     * 生成随机字体
     *
     * @return 随机字体
     */
    private Font randomFont(int fsize) {
        int index = random.nextInt(fontNames.length);
        String fontName = fontNames[index];
        int style = random.nextInt(4);  //生成随机样式，0:无样式，1:粗体，2:斜体，3:粗体+斜体
        return new Font(fontName, style, fsize);
    }

    /**
     * 绘制干扰线
     *
     * @param image  图片
     * @param width  图片宽度
     * @param height 图片高度
     */
    private void drawLine(BufferedImage image, int width, int height) {
        int num = 3; //总共画三条干扰线
        Graphics2D g2 = (Graphics2D) image.getGraphics();
        for (int i = 0; i < num; i++) { //生成两个点的左边，即4个值
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            g2.setStroke(new BasicStroke(1.5F));
            g2.setColor(Color.BLUE);   //设置干扰线颜色为蓝色
            g2.drawLine(x1, y1, x2, y2);
        }
    }

    /**
     * 随机生成一个字符
     *
     * @return 随机字符
     */
    private char randomChar() {
        String codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ";
        int index = random.nextInt(codes.length());
        return codes.charAt(index);
    }

    /**
     * 创建图片缓冲区
     *
     * @return 图片缓冲区
     */
    private BufferedImage createImage(int width, int height) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setColor(backGroudColor);
        graphics.fillRect(0, 0, width, height);
        graphics.setColor(Color.white);
        graphics.drawRect(0, 0, width - 1, height - 1);
        return image;
    }

    /**
     * X轴扭曲图片
     *
     * @param graphics 画布
     * @param width    宽度
     * @param height   高度
     * @param color    背景色
     */
    private void shearX(Graphics graphics, int width, int height, Color color) {
        Random random = new Random();
        int period = 2;
        int frames = 1;
        int phase = random.nextInt(2);
        for (int i = 0; i < height; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (2.2831853071795862D * (double) phase) / (double) frames);
            graphics.copyArea(0, i, width, 1, (int) d, 0);
            graphics.setColor(color);
            graphics.drawLine((int) d, i, 0, i);
            graphics.drawLine((int) d + width, i, width, i);
        }
    }

    /**
     * Y轴扭曲图片
     *
     * @param graphics 画布
     * @param width    宽度
     * @param height   高度
     * @param color    背景色
     */
    private static void shearY(Graphics graphics, int width, int height, Color color) {
        Random random = new Random();
        int period = random.nextInt(40) + 10;
        int frames = 20;
        int phase = random.nextInt(2);
        for (int i = 0; i < width; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (2.2831853071795862D * (double) phase) / (double) frames);
            graphics.copyArea(i, 0, 1, height, 0, (int) d);
            graphics.setColor(color);
            graphics.drawLine(i, (int) d, i, 0);
            graphics.drawLine(i, (int) d + height, i, height);
        }
    }
}
